﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace ImapX
{
    public class ImapReader 
    {
        Stream _stream;

        const int _bufferSize = 1024;
        byte[] _buffer = new byte[_bufferSize];

        int bufferPos = 0;  // next position to read from

        int _bl = 0;
        int bufferLast {
            get
            {
                return _bl;
            }
            set
            {
                _bl = value;
            }
        }   // last valid position to read from

        Encoding DefaultEncoding { get; set; }

        public ImapReader(Stream stream, Encoding encoding = null) 
        { 
            _stream = stream;
            if (encoding == null)
            {
                DefaultEncoding = Encoding.ASCII;
            }
            else
            {
                DefaultEncoding = encoding;
            }
        }

        public Stream BaseStream { get { return _stream;}}

        public String ReadLine()
        {
            return ReadLine(DefaultEncoding);
        }

        // Reads a line. A line is defined as a sequence of characters followed by
        // a carriage return ('\r'), a line feed ('\n'), or a carriage return 
        // immediately followed by a line feed. The resulting string does not 
        // contain the terminating carriage return and/or line feed. The returned
        // value is null if the end of the input stream has been reached. 
        //
        [System.Security.SecuritySafeCritical]  // auto-generated
        public String ReadLine(Encoding encoding)
        {
            if (bufferPos == bufferLast)
            {
                if (ReadBuffer() == 0) return null;
            }
            StringBuilder sb = new StringBuilder();
            do
            {
                int i = bufferPos;
                do
                {
                    char ch = (char)_buffer[i];
                    // Note the following common line feed chars: 
                    // \n - UNIX   \r\n - DOS   \r - Mac 
                    if (ch == '\r' || ch == '\n')
                    {
                        sb.Append(encoding.GetString(_buffer, bufferPos, i - bufferPos));
                        String s = sb.ToString();

                        bufferPos = i + 1;
                        if (ch == '\r' && (bufferPos < bufferLast || ReadBuffer() > 0))
                        {
                            if (_buffer[bufferPos] == '\n') bufferPos++;
                        }
                        return s;
                    }
                    i++;
                } while (i < bufferLast);
                i = bufferLast - bufferPos;
                sb.Append(encoding.GetString(_buffer, bufferPos, i));
                bufferPos += i;
            } while (bufferPos < bufferLast || ReadBuffer() > 0);
            return sb.ToString();
        }

        public int Read(byte[] destBuffer, int offset, int requestedLength)
        {
            int copied = 0;
            int destBufferPos = offset;

            if (bufferPos == bufferLast && ReadBuffer() == 0) return 0;

            while (copied < requestedLength)
            {
                // cache this
                int bufferAvail = this.bufferAvail;
                int leftToRead = requestedLength - copied;

                // Can we read the rest from the buffer?
                if (leftToRead < bufferAvail)
                {
                    Array.ConstrainedCopy(_buffer, bufferPos, destBuffer, destBufferPos, leftToRead);
                    bufferPos += leftToRead;
                    copied += leftToRead;

                    return copied;
                }

                // No? Copy what we can
                Array.ConstrainedCopy(_buffer, bufferPos, destBuffer, destBufferPos, bufferAvail);
                bufferPos += bufferAvail;
                destBufferPos += bufferAvail;
                copied += bufferAvail;

                if (ReadBuffer() == 0) break;
            }
            return copied;
        }

        private int bufferAvail { get { return bufferLast - bufferPos; } }

        int ReadBuffer()
        {
            if (bufferPos == bufferLast) { bufferPos = 0; bufferLast = 0; }

            int read = _stream.Read(_buffer, bufferPos, _bufferSize - bufferPos);

            Debug.Assert(bufferLast + read <= _bufferSize);
            bufferLast += read;

            return read;
        }

        public void Close()
        {
            if (_stream != null)
            {
                _stream.Close();
            }
        }
    }
}
